package io.jsen.meta.diff.context;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import io.jsen.meta.diff.field.MetaFieldDefine;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 字段定义，要diff的类（包括递归的子类）首先需要加载到字段定义中
 * <p>
 * 使用typeToke保存泛型
 *
 * @author jsen
 * @date 2021/4/7
 */
@SuppressWarnings("all")
class MetaFieldDefineContext {

    private static final HashBasedTable<TypeToken, Field, MetaFieldDefine> FIELD_DEFINE = HashBasedTable.create();
    private static final Map<TypeToken, MetaFieldDefine> CLAZZ_DEFINE = Maps.newHashMap();

    static <T> void register(TypeToken<T> typeToken) {
        if (CLAZZ_DEFINE.containsKey(typeToken)) {
            return;
        }
        Class<?> clazz = typeToken.getRawType();
        parseClazz(typeToken);
        if (Map.class.isAssignableFrom(clazz)
                || List.class.isAssignableFrom(clazz)) {
            return;
        }
        parseFields(typeToken);
    }

    private static <T> void parseClazz(TypeToken<T> typeToken) {
        // 解析 typeToken
        List<Class<?>> genericTypeList = new ArrayList<>();
        Type genericType = typeToken.getType();
        if (genericType instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) genericType;
            for (Type type : pt.getActualTypeArguments()) {
                genericTypeList.add((Class<?>) type);
            }
        }
        putClazz(new MetaFieldDefine(typeToken, genericTypeList));
        for (Class<?> clazz : genericTypeList) {
            register(TypeToken.of(clazz));
        }
    }

    private static <T> void parseFields(TypeToken<T> typeToken) {
        Class<?> clazz = typeToken.getRawType();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) {
                continue;
            }
            List<Class<?>> genericTypeList = new ArrayList<>();
            Type genericType = field.getGenericType();
            if (genericType instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) genericType;
                for (Type type : pt.getActualTypeArguments()) {
                    genericTypeList.add((Class<?>) type);
                }
            }
            for (Class<?> claz : genericTypeList) {
                register(TypeToken.of(claz));
            }
            register(TypeToken.of(field.getType()));
            put(new MetaFieldDefine(typeToken, field, genericTypeList));
        }
    }


    @SuppressWarnings("unused")
    static <T> MetaFieldDefine getFieldDefine(TypeToken<T> typeToken, Field field) {
        return FIELD_DEFINE.get(typeToken, field);
    }

    static <T> MetaFieldDefine getClazzDefine(TypeToken<T> typeToken) {
        return CLAZZ_DEFINE.get(typeToken);
    }

    static <T> List<MetaFieldDefine> getClazzFields(TypeToken<T> typeToken) {
        Map<Field, MetaFieldDefine> map = FIELD_DEFINE.row(typeToken);
        if (map == null) {
            return Lists.newArrayList();
        }
        return map.values().stream().filter(item -> item.getField() != null)
                .collect(Collectors.toList());
    }

    private static void put(MetaFieldDefine define) {
        FIELD_DEFINE.put(define.getTypeToken(), define.getField(), define);
    }

    private static void putClazz(MetaFieldDefine define) {
        CLAZZ_DEFINE.put(define.getTypeToken(), define);
    }

    private MetaFieldDefineContext() {
    }
}
